﻿using System;
using System.Collections.Generic;
using System.Data;
using Loki.Model;
using Loki.Resources;
using Loki.Utils;

namespace Loki.Data
{
    public abstract class LinkMapper<TLink, TDataEntityKey> : CommandMapper
        where TLink : IEntityLink<TDataEntityKey>
    {
        #region Properties set by spring

        public string GetLeftRequest { get; set; }

        public string GetRightRequest { get; set; }

        public string GetAllRequest { get; set; }

        public string CreateRequest { get; set; }

        public string DeleteRequest { get; set; }

        public string LeftParameterName { get; set; }

        public string RightParameterName { get; set; }

        #endregion Properties set by spring

        #region Factory

        /// <summary>
        /// Gets or sets the factory used for building entities during load.
        /// </summary>
        public IFactory<TLink> Factory
        {
            get;
            protected set;
        }

        /// <summary>
        /// Gets or sets the discriminator field wich is passed to the factory.
        /// </summary>
        public string Discriminator
        {
            get;
            protected set;
        }

        #endregion Factory

        #region General builders

        /// <summary>
        /// Builds one entity from data record.
        /// </summary>
        /// <param name="P_Record">The record.</param>
        /// <returns>Loaded link.</returns>
        private IEntityLink<TDataEntityKey> BuildOne(IDataReader P_Record)
        {
            // define factory creator
            Func<IDataReader, IEntityLink<TDataEntityKey>> L_Creator = null;

            // select factory call
            if (string.IsNullOrEmpty(Discriminator))
            {
                L_Creator = (x) => Factory.Create(null);
            }
            else
            {
                L_Creator = (x) => Factory.Create(x[Discriminator]);
            }

            return CommandMapper.BuildOne<IEntityLink<TDataEntityKey>>(P_Record, L_Creator, LoadFromDataRecord);
        }

        /// <summary>
        /// Builds an entity list from data reader.
        /// </summary>
        /// <param name="P_Reader">The reader.</param>
        /// <returns>List of loaded links.</returns>
        private IEnumerable<IEntityLink<TDataEntityKey>> BuildList(IDataReader P_Reader)
        {
            // define factory creator
            Func<IDataReader, IEntityLink<TDataEntityKey>> L_Creator = null;

            // select factory call
            if (string.IsNullOrEmpty(Discriminator))
            {
                L_Creator = (x) => Factory.Create(null);
            }
            else
            {
                L_Creator = (x) => Factory.Create(x[Discriminator]);
            }

            return CommandMapper.BuildList<IEntityLink<TDataEntityKey>>(P_Reader, L_Creator, LoadFromDataRecord);
        }

        #endregion General builders

        private const string REQUEST_TYPE_GET = "GET";
        private const string REQUEST_TYPE_GET_ALL = "GET ALL";
        private const string REQUEST_TYPE_GET_CREATE = "CREATE";
        private const string REQUEST_TYPE_GET_ALL_LEFT = "GET ALL LEFT";
        private const string REQUEST_TYPE_GET_ALL_RIGHT = "GET ALL RIGHT";
        private const string REQUEST_TYPE_GET_DELETE = "DELETE";

        /// <summary>
        /// Loads the entity from data record.
        /// </summary>
        /// <param name="P_Entity">The entity.</param>
        /// <param name="P_Record">The data record.</param>
        public virtual void LoadFromDataRecord(IEntityLink<TDataEntityKey> P_Entity, IDataRecord P_Record)
        {
            // Load core
            try
            {
                P_Entity.Left = DataConverter(P_Record, LeftParameterName);
                P_Entity.Right = DataConverter(P_Record, RightParameterName);
            }
            catch (Exception Ex)
            {
                throw BuildErrorFormat<LokiException>(ErrorMessages.Data_Mappers_ReadError, Ex, this.GetType().Name, P_Entity.Left, P_Entity.Right);
            }
        }

        protected abstract Func<IDataRecord, string, TDataEntityKey> DataConverter { get; }

        public virtual IEnumerable<IEntityLink<TDataEntityKey>> GetAll(IDbTransaction P_Transaction)
        {
            if (string.IsNullOrEmpty(GetAllRequest))
            {
                throw BuildErrorFormat<ArgumentException>(ErrorMessages.Data_Mappers_NoRequestDefinied, REQUEST_TYPE_GET_ALL, GetType().Name);
            }

            return ExecuteGetAll(P_Transaction, GetAllRequest, null);
        }

        public virtual IEnumerable<IEntityLink<TDataEntityKey>> GetAllLeft(IDbTransaction P_Transaction, TDataEntityKey P_LeftValue)
        {
            if (string.IsNullOrEmpty(GetLeftRequest))
            {
                throw BuildErrorFormat<ArgumentException>(ErrorMessages.Data_Mappers_NoRequestDefinied, REQUEST_TYPE_GET_ALL_LEFT, GetType().Name);
            }

            return ExecuteGetAll(P_Transaction, GetLeftRequest, (x) => MapGetLeftParameter(x, P_LeftValue));
        }

        public virtual IEnumerable<IEntityLink<TDataEntityKey>> GetAllRight(IDbTransaction P_Transaction, TDataEntityKey P_LeftValue)
        {
            if (string.IsNullOrEmpty(GetRightRequest))
            {
                throw BuildErrorFormat<ArgumentException>(ErrorMessages.Data_Mappers_NoRequestDefinied, REQUEST_TYPE_GET_ALL_RIGHT, GetType().Name);
            }

            return ExecuteGetAll(P_Transaction, GetRightRequest, (x) => MapGetRightParameter(x, P_LeftValue));
        }

        protected virtual void MapGetLeftParameter(IDatabaseCommand P_Command, TDataEntityKey P_LeftValue)
        {
            P_Command.AddParameter(LeftParameterName, P_LeftValue);
        }

        protected virtual void MapGetRightParameter(IDatabaseCommand P_Command, TDataEntityKey P_RightValue)
        {
            P_Command.AddParameter(RightParameterName, P_RightValue);
        }

        public virtual IEntityLink<TDataEntityKey> ExecuteGet(IDbTransaction P_Transaction, string P_CommandName, Action<IDatabaseCommand> P_MapParameters)
        {
            IDatabaseCommand L_Command = PrepareCommand(P_CommandName, P_MapParameters);
            try
            {
                IDataReader L_CommandReturn = Database.ExecuteQuery(L_Command, P_Transaction);
                return BuildOne(L_CommandReturn);
            }
            catch (Exception Ex)
            {
                throw BuildErrorFormat<LokiException>(ErrorMessages.Data_Mappers_QueryError, Ex, this.GetType().Name, L_Command.Dump());
            }
        }

        public virtual IEnumerable<IEntityLink<TDataEntityKey>> ExecuteGetAll(IDbTransaction P_Transaction, string P_CommandName, Action<IDatabaseCommand> P_MapParameters)
        {
            IDatabaseCommand L_Command = PrepareCommand(P_CommandName, P_MapParameters);
            try
            {
                IDataReader L_CommandReturn = Database.ExecuteQuery(L_Command, P_Transaction);

                return BuildList(L_CommandReturn);
            }
            catch (Exception Ex)
            {
                throw BuildErrorFormat<LokiException>(ErrorMessages.Data_Mappers_QueryError, Ex, this.GetType().Name, L_Command.Dump());
            }
        }

        public virtual void Delete(IDbTransaction P_Transaction, IEntityLink<TDataEntityKey> P_Link)
        {
            if (string.IsNullOrEmpty(DeleteRequest))
            {
                throw BuildErrorFormat<ArgumentException>(ErrorMessages.Data_Mappers_NoRequestDefinied, REQUEST_TYPE_GET_DELETE, GetType().Name);
            }

            IDatabaseCommand L_Command = Database.CreateCommand(DeleteRequest);
            L_Command.AddParameter(LeftParameterName, P_Link.Left);
            L_Command.AddParameter(RightParameterName, P_Link.Right);
            try
            {
                Database.ExecuteNonQuery(L_Command, P_Transaction);
            }
            catch (Exception Ex)
            {
                throw BuildErrorFormat<LokiException>(ErrorMessages.Data_Mappers_DeleteError, Ex, this.GetType().Name, L_Command.Dump());
            }
        }

        public virtual void Create(IDbTransaction P_Transaction, IEntityLink<TDataEntityKey> P_Link)
        {
            if (string.IsNullOrEmpty(CreateRequest))
            {
                throw BuildErrorFormat<ArgumentException>(ErrorMessages.Data_Mappers_NoRequestDefinied, REQUEST_TYPE_GET_CREATE, GetType().Name);
            }

            IDatabaseCommand L_Command = Database.CreateCommand(CreateRequest);
            L_Command.AddParameter(LeftParameterName, P_Link.Left);
            L_Command.AddParameter(RightParameterName, P_Link.Right);
            try
            {
                Database.ExecuteNonQuery(L_Command, P_Transaction);
            }
            catch (Exception Ex)
            {
                throw BuildErrorFormat<LokiException>(ErrorMessages.Data_Mappers_SaveError, Ex, this.GetType().Name, L_Command.Dump());
            }
        }
    }
}